package redisclient;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class GetCommand implements Command {


    private static Logger logger = LoggerFactory.getLogger(GetCommand.class);

    // 两个字符/r/n长度
    public static final int NEWLINE_CHARCOUNT = 2;

    String key;
    String value;
    boolean done = false;
    ByteBuffer lastbuf;

    int readline = 0;
    int readdata = 1;
    int state = readline;
    int datalength = 0;

    public GetCommand(String key) {
        this.key = key;
    }

    public void setValue(String value) {
        synchronized (this) {
            this.done = true;
            this.value = value;
            this.notifyAll();
        }
    }

    @Override
    public ByteBuffer toByteBuffer() {
        StringBuilder command = new StringBuilder();
        String k = this.key;
        command.append("*2").append("\r\n");
        command.append("$3").append("\r\n");
        command.append("GET").append("\r\n");
        command.append("$").append(k.getBytes().length).append("\r\n");
        command.append(k).append("\r\n");
        return ByteBuffer.wrap(command.toString().getBytes());
    }

    @Override
    public void read(SocketChannel socketChannel) throws IOException {

        ByteBuffer bufdata = (this.lastbuf == null) ? readnew(socketChannel) : combine2(this.lastbuf, readnew(socketChannel));

        if (this.state == readline) {
            String line = readline(bufdata);
            if (line == null) {
                bufdata.rewind();
                this.lastbuf = bufdata;
                return;
            }

            if (line.charAt(0) == '$') {
                int length = Integer.parseInt(line.substring(1, line.length() - 2));

                if (length == -1) {
                    this.setValue(null);
                } else if (length == 0) {
                    this.setValue("");
                } else {
                    value = readstring(bufdata, length + NEWLINE_CHARCOUNT);
                    if (value != null) {
                        this.setValue(value);
                    } else {
                        this.lastbuf = bufdata;
                        this.state = readdata;
                        this.datalength = length;
                    }
                }
            }else {
                throw new RuntimeException();
            }

        }else if(this.state == readdata){
            value = readstring(bufdata, this.datalength + NEWLINE_CHARCOUNT);
            if (value != null) {
                this.setValue(value);
            } else {
                this.lastbuf = bufdata;
                this.state = readdata;
            }
        }

    }

    private ByteBuffer combine2(ByteBuffer buf, ByteBuffer buf2) {
        return (ByteBuffer)ByteBuffer.allocate(buf.remaining() + buf2.remaining()).put(buf).put(buf2).flip();
    }

    private ByteBuffer readnew(SocketChannel socketChannel) throws IOException {
        ByteBuffer buf = ByteBuffer.allocate(1024);
        int bytesRead = socketChannel.read(buf);
        logger.info("read data,len={}", bytesRead);
        buf.flip();
        return buf;
    }

    private String readstring(ByteBuffer buf, int length) {
        int remaining = buf.remaining();
        logger.info("remaining={}, length={}", remaining, length);
        if (remaining == length) {
            byte[] bytes = new byte[length - NEWLINE_CHARCOUNT];
            buf.get(bytes);
            return new String(bytes);
        }
        return null;
    }

    private String readline(ByteBuffer buf) {

        int normal = 0;
        int lastisr = 1;

        int state = normal;

        StringBuilder builder = new StringBuilder();
        while (true) {
            if (!buf.hasRemaining()) {
                break;
            }

            byte b = buf.get();
            builder.append((char) b);
            if (b == '\r') {
                state = lastisr;
                continue;
            }

            if (b == '\n'
                    && state == lastisr) {
                return builder.toString();
            }

            state = normal;

        }

        return null;
    }

    public boolean isDone() {
        return done;
    }

    public String get() {
        try {
            while (true) {
                synchronized (this) {
                    if (this.isDone()) {
                        return value;
                    } else {
                        this.wait();

                    }
                }
            }
        } catch (InterruptedException e) {
            throw new RedisException(e);
        }
    }


}
